package solver

import (
	"log"
)

/*
In this sumupCandsHouse(), the return values *candHouses* express which houses the candidates are in,
e.g., candHouses[1]=5, means that candidate 1 is in only row 5, col 5, or box 5.
- if candHouses[cand] > 0, candHouses[cand] is the number of the house.
- if candHouses[cand] = -1, the candidate does not appear in any house.
- if candHouses[cand] = -2, the candidate appear in multiple houses.
*/
func (g *Grid) sumupCandsHouse(house House, funcNum func(*Cell) int) (candHouses [9]int) {
	candHouses = [9]int{-1, -1, -1, -1, -1, -1, -1, -1, -1}
	for _, cell := range house.cells {
		if cell.solved() {
			continue
		}
		for cand, candidate := range cell.candidates {
			if !candidate || candHouses[cand] == funcNum(cell) {
				continue
			}
			if candHouses[cand] == -1 {
				candHouses[cand] = funcNum(cell)
			} else {
				candHouses[cand] = -2
			}
		}
	}
	return
}

func (g *Grid) clearupLineInPointing(cand int, line House, butBoxNum int) (used bool) {
	for _, cell := range line.cells {
		if cell.solved() || cell.boxNum == butBoxNum {
			continue
		}
		if cell.removeCandidate(cand) {
			used = true
		}
	}
	return
}

func (g *Grid) rowPointingBox(boxNum int) (used bool) {
	candRows := g.sumupCandsHouse(g.boxes[boxNum], func(c *Cell) int { return c.rowNum })
	for cand, rowNum := range candRows {
		if rowNum < 0 {
			continue
		}
		if g.clearupLineInPointing(cand, g.rows[rowNum], boxNum) {
			used = true
			log.Printf("b%vr%v %v %v", boxNum, rowNum, cand+1, "Row Pointing")
		}
	}
	return
}

func (g *Grid) colPointingBox(boxNum int) (used bool) {
	candCols := g.sumupCandsHouse(g.boxes[boxNum], func(c *Cell) int { return c.colNum })
	for cand, colNum := range candCols {
		if colNum < 0 {
			continue
		}
		if g.clearupLineInPointing(cand, g.cols[colNum], boxNum) {
			used = true
			log.Printf("b%vc%v %v %v", boxNum, colNum, cand+1, "Col Pointing")
		}
	}
	return
}

func (g *Grid) Pointing() (used bool) {
	for boxNum := 0; boxNum < 9; boxNum++ {
		if g.rowPointingBox(boxNum) {
			used = true
		}
		if g.colPointingBox(boxNum) {
			used = true
		}
	}
	return
}

func (g *Grid) clearupBoxInClaiming(cand int, box House, butLineNum int,
	funcNum func(*Cell) int) (used bool) {
	for _, cell := range box.cells {
		if cell.solved() || funcNum(cell) == butLineNum {
			continue
		}
		if cell.removeCandidate(cand) {
			used = true
		}
	}
	return
}

func (g *Grid) claimingRow(row House) (used bool) {
	candsBox := g.sumupCandsHouse(row, func(c *Cell) int { return c.boxNum })
	for cand, boxNum := range candsBox {
		if boxNum < 0 {
			continue
		}
		rowNum := row.cells[0].rowNum
		if g.clearupBoxInClaiming(cand, g.boxes[boxNum], rowNum,
			func(c *Cell) int { return c.rowNum }) {
			used = true
			log.Printf("b%vr%v %v %v", boxNum, rowNum, cand+1, "Row Claiming")
		}
	}
	return
}

func (g *Grid) claimingCol(col House) (used bool) {
	candsBox := g.sumupCandsHouse(col, func(c *Cell) int { return c.boxNum })
	for cand, boxNum := range candsBox {
		if boxNum < 0 {
			continue
		}
		colNum := col.cells[0].colNum
		if g.clearupBoxInClaiming(cand, g.boxes[boxNum], colNum,
			func(c *Cell) int { return c.colNum }) {
			used = true
			log.Printf("b%vc%v %v %v", boxNum, colNum, cand+1, "Col Claiming")
		}
	}
	return
}

func (g *Grid) Claiming() (used bool) {
	for _, row := range g.rows {
		if g.claimingRow(row) {
			used = true
		}
	}

	for _, col := range g.cols {
		if g.claimingCol(col) {
			used = true
		}
	}
	return
}
